package gggameserver;

import gggameserver.compiler.GGSolutionData;
import gggameserver.dbaccess.GGDBAccess;
import gggameserver.dbaccess.GGMySqlDBAccess;
import gggameserver.dbaccess.data.GGDBAGameInfo;
import gggameserver.exceptions.GGException;
import gggameserver.exceptions.GGNoDataFoundException;
import gggameserver.exceptions.GGTimeout;
import gggameserver.games.Board;
import gggameserver.games.Game;
import gggameserver.games.GameJudge;
import gggameserver.games.GameResult;
import gggameserver.games.connection.GGConnection;
import gggameserver.games.khet.KhetBoard;
import gggameserver.games.khet.KhetPlayer;
import gggameserver.games.stopgate.StopGate;
import gggameserver.testbed.GGTestBed;
import gggameserver.testbed.GGTestBedFactory;
import gggameserver.util.GGConfiguration;
import gggameserver.util.GGUtil;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.log4j.Logger;

public class GGGameServer implements Runnable {
	
    private static org.apache.log4j.Logger log = Logger.getLogger(GGGameServer.class);
    
//	private void test() {
//		// TODO Auto-generated method stub
////		testCompileLocal("Python",
////				"D:/prog/olympiaadid/manguvoistlus/est-ai/branches/" + 
////				"test_kristo/saadetud python'i n�ide/" + 
////				"Pydemo_katki.zip"
////				//"Pydemo.orig.zip"
////				);
//
////		testCompileLocalTest("Python",
////				"D:/prog/olympiaadid/manguvoistlus/est-ai/branches/" + 
////				"test_kristo/saadetud python'i n�ide/" + 
////				"Pydemo"
////				);
//
//		testCompileLocalTest("java",
//				"D:/prog/olympiaadid/manguvoistlus/est-ai/branches/test_kristo/" + 
//				"t1/t_java2"
//		);
//		
//		throw new IllegalStateException("test");
//	}

    private GGTestBedFactory testbed_fac = null;
    private GGTestBed tester_testbed = null;
    private boolean usingSlaves = false;
	
void timeout() {
	try {
		Connection con = GGMySqlDBAccess.getInstance().getConnection();
		Thread.sleep(65*1000);
		con = GGMySqlDBAccess.getInstance().getConnection();
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	throw new IllegalStateException("timeout...test");
}

	GGDBAccess dbaccess;

	public GGGameServer(GGDBAccess dba) throws FileNotFoundException, IOException, GGNoDataFoundException {
		dbaccess = dba;
		
//timeout();
		log.info("Initializing GGGameServer...");

		log.info("Reading properties...");
		//props = new Properties();
		//props.load(new FileReader("gggameserver.ini"));
		//GGConfiguration.getInstance();
		
		log.info("Initializing test-bed factory...");
		testbed_fac = GGTestBedFactory.createTestBedFactory();
		
//		usingSlaves = GGConfiguration.getBooleanProperty("gggameserver.use_slaves");
//		if (usingSlaves) {
//			log.info("Using slaves...");
//			testbed_fac.enableRemoteTestBed(
//					GGConfiguration.getProperty("gggameslave.server_host"),
//					Integer.parseInt(GGConfiguration.getProperty("gggameslave.server_port"))
//					); // TODO parse exception 
//		}
		
//		log.info("Creating tester test-bed...");
//		tester_testbed = createTesterTestBed();
		
		log.info("Initializing GGGameServer DONE.");
		
//		test();
	}

//	private GGTestBed createTesterTestBed() throws GGNoDataFoundException {
//		GGSolutionData test = new GGSolutionData(
//				GGConfiguration.getProperty("testbed.tester_lang"), 
//				GGConfiguration.getProperty("testbed.tester"));
//		
//		return this.testbed_fac.prepareTestBed(test);
//	}

	@Override
	public void run() {
		log.info("Starting server...");

		String file_run_name = "delete_to_stop_ggserver.run";
		log.info("Creating run-lock...");
		File file_run = new File(file_run_name);
		try {
			if (!file_run.exists()) file_run.createNewFile();
		} catch (IOException e1) {
			e1.printStackTrace();
			log.fatal("Couldn't create run-lock file", e1);
			return;
		}
		
		try {
			while (true) {
				if (!file_run.exists()) break;
				
				boolean was_game = false;
				try {
					was_game = playGame();
				} catch (IllegalStateException e) {
					// currently occurred one case, where
					// DB said that solutution compiles, but actually
					// didn't and GGMirroredTestBed throw exception on init
					//
					e.printStackTrace();
				}
				
				if (!was_game) GGUtil.sleep(15000);
				else GGUtil.sleep(GGConfiguration.getLongProperty(
						"gggameserver.sleep_between", 5000));
			}
			
//			tester_testbed.close();
//if(false)			while (testCompileProgram());
//			this.testbed_fac.prepareTestBed(new GGSolutionData("java", "test1.zip"));
//if (false)			playGame();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (GGNoDataFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (GGException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		log.info("DONE.");
	}
	
	private GGTestBed getTestBed(GGSolutionData sol, int nr) {
		return getTestBed(this.testbed_fac, sol, nr);
	}
	
	private static GGTestBed getTestBed(GGTestBedFactory tbf, GGSolutionData sol, int nr) {
		return tbf.prepareMirroredTestBed("compile", sol, nr);
	}

//	public boolean testCompileProgram() throws ClassNotFoundException, SQLException, GGException {
//		Connection con = GGMySqlDBAccess.getInstance().getConnection();
//		
//		PreparedStatement stmt =con.prepareStatement("select min(id) first_id from v_latest_programs where compile_result = 'N'");
//		ResultSet res = stmt.executeQuery();
//		if (!res.next())
//			throw new GGException("Error while finding uncompiled program");
//		
//		long prog_id = res.getLong(1);
//		boolean was_null = res.wasNull();
//		res.close();
//		
//		if (was_null) {
//			log.info("No uncompiled program found");
//			return false;
//		} else {
//			log.info("Found uncompiled program programs.id = " + prog_id);
//			testCompileProgram(con, prog_id);
//			return true;
//		}
//	}
	
	/**
	 * @param con
	 * @param prog_id
	 * @return true if compile was successful
	 */
	public boolean testCompileProgram(long prog_id) {
		GGSolutionData sol = this.dbaccess.getSolutionDataByProgramId(prog_id);
		if (null == sol) return false;

		GGTestBed tb1 = null;
		GGTestBed tb2 = null;
		try {
			tb1 = getTestBed(sol, 1);
			if (tb1.isValid()) {
				//tb2 = getTestBed(sol, 2);
				
				if (true) {
				//if (tb2.isValid()) {
					try {
						StringBuilder log = new StringBuilder();
						log.append("========== Compiler log ==========\n");
						log.append(tb1.getLog());
						log.append("\n\n\n");

if (false) throw new IOException();
//						String testPlayLog = testPlayGame(tb1, tb2);
//
//						log.append("========== Test play ==========\n");
//						log.append(tb1.getLog());
//						log.append("\n\n\n");
						
						setCompileSuccessful(prog_id, log.toString());
						return true;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						setCompileFailed(prog_id, e.toString());
						return false;
					}
				} else {
					setCompileFailed(prog_id, tb1.getLog());
					return false;
				}
			} else {
				setCompileFailed(prog_id, tb1.getLog());
				return false;
			}
			
		} finally {
			if (tb1 != null) tb1.close();
			if (tb2 != null) tb2.close();
		}
	}
	
	public String testPlayGame(GGTestBed tb1, GGTestBed tb2) throws IOException {
		// shorter game for test
// TODO hack		GameResult res = playLahing(new File("."), tb1, tb2, null, 600);
		
		return null; 
	}

//	// TODO copy!!!
//	public boolean testCompileLocal(String lang, String file) {
//		GGSolutionData sol;
//		try {
//			sol = new GGSolutionData(lang, file);
//		} catch (GGNoDataFoundException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//			return false; // TODO
//		} // TODO by program id
//
//		GGTestBed tb = null;
//		try {
//			tb = this.testbed_fac.prepareTestBed(sol);
//			if (tb.isValid()) {
//				try {
//					GameJudge judge = playGame(tb, tester_testbed);
//System.out.println("Game log:" + judge.getGameLogText());
//System.out.println("Test-compiles successful");
//					return true;
//				} catch (IOException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//System.out.println("Test-compiles failed: " + e.toString());
//					return false;
//				}
//			} else {
//System.out.println("Test-compiles failed: " + tb.getLog());
//				return false;
//			}
//		} finally {
//			if (tb != null) tb.close();
//		}
//	}
	
	// TODO copy!!!
//	public boolean testCompileLocalTest(String lang, String dir) {
//		GGTestBed tb = this.testbed_fac.prepareLocalTestTestBed(new File(dir), lang);
//		try {
//			//
//			if (tb.isValid()) {
//				try {
//					GameJudge judge = playGame(tb, tester_testbed);
//System.out.println("Game log:" + judge.getGameLogText());
//System.out.println("Test-compiles successful");
//					return true;
//				} catch (IOException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//System.out.println("Test-compiles failed: " + e.toString());
//					return false;
//				}
//			} else {
//System.out.println("Test-compiles failed: " + tb.getLog());
//				return false;
//			}
//		} finally {
//			if (tb != null) tb.close();
//		}
//	}
	
	public boolean setCompileSuccessful(long prog_id, String log) {
		return setCompileResult(prog_id, 'S', log);
	}
	
	public boolean setCompileFailed(long prog_id, String log) {
		return setCompileResult(prog_id, 'F', log);
	}
	
	public boolean setCompileResult(long prog_id, char result, String log) {
		return this.dbaccess.setCompileResult(prog_id, result, log);
	}
	
	public boolean playGame() throws ClassNotFoundException, SQLException, GGNoDataFoundException {
		GGDBAGameInfo gameInfo = this.dbaccess.getNewGame();
		if (null == gameInfo) return false;
		
		long p_game_id = gameInfo.gameId;
		long p_solution1_id = gameInfo.solutionId_1;
		long p_solution2_id = gameInfo.solutionId_2;
		
		log.info("p_id = " + p_game_id);
		log.info("p_solution1_id = " + p_solution1_id);
		log.info("p_solution2_id = " + p_solution2_id);
		
		// data
		GGSolutionData sol1 = this.dbaccess.getSolutionData(p_solution1_id);
		GGSolutionData sol2 = this.dbaccess.getSolutionData(p_solution2_id);
		
/* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */
		if (!ensureSolutionCompiled(sol1)) {
			log.info("Can't play. Solution " + 
					sol1.getSolutionID() + " (prog_id=" + sol1.getProgramID() + 
					") can't be compiled.");
this.dbaccess.deleteAvailableGame(p_game_id);
			return true;
		}
		if (!ensureSolutionCompiled(sol2)) {
			log.info("Can't play. Solution " + 
					sol2.getSolutionID() + " (prog_id=" + sol2.getProgramID() + 
					") can't be compiled.");
this.dbaccess.deleteAvailableGame(p_game_id);
			return true;
		}
/*
*/
	
		GGGameServerGameResult ladderGame = playCompiledGame(this.testbed_fac, sol1, sol2);
		
		if (null != ladderGame) {
			long prog_1 = sol1.getProgramID();
			long prog_2 = sol2.getProgramID();
			
			Long prog_winner = null;
			if (ladderGame.getWinner() == 1) prog_winner = prog_1;
			if (ladderGame.getWinner() == 2) prog_winner = prog_2;
			
			log.info("Submitting game result p_game_id=" + p_game_id + " prog_1=" + 
					prog_1 + ", prog_2=" + prog_2 +
					", winner=" + prog_winner);
			//p_post_ladder_game_results

			if ( this.dbaccess.submitGameResults(p_game_id, prog_1, prog_2, prog_winner, ladderGame) ) { 
				log.info("Game info submitted");
			} else {
				log.warn("Can't submit game info");
			}
		}

		return true;
	}
	
	public static GGGameServerGameResult playCompiledGame(GGTestBedFactory testbed_fac, GGSolutionData sol1, GGSolutionData sol2) throws ClassNotFoundException, SQLException, GGNoDataFoundException {
		GGTestBed tb1 = null;
		GGTestBed tb2 = null;
		
		GGGameServerGameResult result = null;
		
		try {
			tb1 = getTestBed(testbed_fac, sol1, 1);
			tb2 = getTestBed(testbed_fac, sol2, 2);
			
			Timestamp start = new Timestamp(Calendar.getInstance().getTimeInMillis());
			GameResult judge = playLahing(tb1, tb2);
			Timestamp end = new Timestamp(Calendar.getInstance().getTimeInMillis());
			
			result = new GGGameServerGameResult(judge, start, end);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (tb1 != null) tb1.close();
			if (tb2 != null) tb2.close();
		}

		return result;
	}
	
	private boolean ensureSolutionCompiled(GGSolutionData sol1) {
		if (null == sol1) {
			log.error("Solution data is empty");
			return false;
		}
		
		String compile_result = sol1.getCompileResult();
		
		if ("S".equals(compile_result)) return true;
		if ("F".equals(compile_result)) return false;
		
		return testCompileProgram(sol1.getProgramID());
	}

	public GameJudge playGame(GGTestBed p1, GGTestBed p2) throws IOException {
		GGConnection c1 = p1.createConnection();
		GGConnection c2 = p2.createConnection();
		
		Game game = new StopGate(12, 12);
		Board board = game.getInitialBoard();
		board.setPlayerConnection(1, c1);
		board.setPlayerConnection(2, c2);

		GameJudge judge = new GameJudge(board);
		judge.playGame();
		
		c1.close();
		c2.close();
		
		return judge;
	}
	
	public static GameResult playLahing(GGTestBed p1, GGTestBed p2) throws IOException {
		ByteArrayOutputStream data = new ByteArrayOutputStream(1000 * 1000); 
		ZipOutputStream zip = new ZipOutputStream(data);
		zip.putNextEntry(new ZipEntry("log.txt"));
		
		PrintWriter out = new PrintWriter(new OutputStreamWriter(zip));
		int winner = playLahing(new File("."), p1, p2, out);
		out.close();
		zip.close();
		data.close();
		
		byte[] bytes = data.toByteArray();
System.out.println("xxxxxxxxx = " + bytes.length);

		
		return new GameResult(winner, bytes); // TODO hack
	}
	
    public static int playLahing(File gameConfDir, GGTestBed p1, GGTestBed p2, PrintWriter gameLog) throws IOException {
        return playKhet(gameConfDir, p1, p2, gameLog, null);
    }
    
    public static Integer playKhet(File gameConfDir, GGTestBed p1, GGTestBed p2, PrintWriter gameLog, Integer timelimit) throws IOException {
        GGConnection c1 = null;
        GGConnection c2 = null;

        Integer winner = null;
        try {
            c1 = p1.createConnection();
            c2 = p2.createConnection();
            
            // prepare game
            KhetBoard khetBoard = new KhetBoard();
            khetBoard.addPlayer(new KhetPlayer(khetBoard));
            khetBoard.addPlayer(new KhetPlayer(khetBoard));
            khetBoard.setPlayerConnection(1, c1);
            khetBoard.setPlayerConnection(2, c2);
            

System.out.println("first line c1 < " + c1.receiveCommand(-1));
System.out.println("first line c2 < " + c2.receiveCommand(-1));
            
            // play game
            GameJudge gj = new GameJudge(khetBoard);
            gj.limiit = 10*1000;
            // TODO timelimit & stuff
            gj.playGame();
            
            // result
            winner = gj.getWinner();
            gameLog.append(gj.getGameLogText());
        } catch (GGTimeout e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (null != c1) c1.close();
            if (null != c2) c2.close();
        }
        
        return winner;
    }

//	public static GameResult playLahing(File gameConfDir, GGTestBed p1, GGTestBed p2, PrintWriter gameLog) throws IOException {
//// TODO hack		return playLahing(gameConfDir, p1, p2, gameLog, null);
//		return null;
//	}
	
//	public static GameResult playLahing(File gameConfDir, GGTestBed p1, GGTestBed p2, PrintWriter gameLog, Integer timelimit) throws IOException {
//		GGConnection c1 = null;
//		GGConnection c2 = null;
//		
//		GameResult result;
//		try {
//			c1 = p1.createConnection();
//			c2 = p2.createConnection();
//			Lahing2011Server server = new Lahing2011Server(gameConfDir, gameLog);
//			
//			server.addServerClient(new GGChannel(c1));
//			server.addServerClient(new GGChannel(c2));
//			
//			server.game.setTimelimit(timelimit);
//			server.game.playGame();
//			
//			// wait till the end
//			result = server.waitGameEnd();
//		} finally {
//			if (null != c1) c1.close();
//			if (null != c2) c2.close();
//		}
//
//		return result;
//	}
	
}
